#include <stdio.h>
#ifdef _GUI
#include "diskTest.h"
static int gui_loop_process(void *_nil);
static void log_out(char *log);
static int break_collect_to;

volatile bool gworking = false;
struct break_collect *break_log;
static struct break_collect *break_log_start;

static struct med_name med_list[] = {
        {"mainui_test_seq", TSEQ, ""},
        {"mainui_test_seed", TSEED, ""},
        {"mainui_write_seq", WSEQ, ""},
        {"mainui_write_seed", WSEED, ""},
        {"mainui_write_random", WRAND, ""},
        {NULL, 0, NULL}
};

static struct job_listen job_listen = {0};
static struct mainui mainui;
static struct conformui conformui;
static struct progressui progressui;
static struct stopui stopui;
static struct texterrui texterrui;
static struct resultui resultui;
static struct aboutpage aboutpage;
static GtkCheckButton **concb_list = NULL;
static GtkTextBuffer *result_buf;
static GtkBuilder *builder;
static void gtk_obj_get(void) {
        #define CLK "clicked"
        #define DTY "destroy"
        #define TERM "delete-event"
        #define con(a, b) a.b = (typeof(a.b))gtk_builder_get_object(builder, #a "_" #b)
        #define conwin(obj) \
                obj.win = (typeof(obj.win))gtk_builder_get_object(builder, #obj); \
                g_signal_connect(obj.win, TERM, G_CALLBACK(obj##_exit), NULL);\
                g_signal_connect(obj.win, DTY, G_CALLBACK(obj##_exit), NULL)
        #define conclk(win, sub) \
                con(win, sub); \
                g_signal_connect(win.sub, CLK, G_CALLBACK(win##_##sub), NULL)
        #define concb(what) \
                con(conformui, chkb_##what); \
                g_signal_connect(conformui.chkb_##what, CLK, G_CALLBACK(conformui_chkb), NULL)
        conwin(aboutpage);
        conwin(mainui);
        conclk(mainui, runit);
        conclk(mainui, exit);
        conclk(mainui, help);
        con(mainui, dev);
        con(mainui, test_seq);
        con(mainui, test_seed);
        con(mainui, write_seq);
        con(mainui, write_seed);
        con(mainui, function);
        con(mainui, test_method);
        con(mainui, write_method);
        conwin(conformui);
        concb(dev);
        concb(size);
        concb(fromto);
        concb(writesize);
        concb(method);
        concb(seed);
        concb(final);
        con(conformui, dev);
        con(conformui, size);
        con(conformui, fromto);
        con(conformui, writesize);
        con(conformui, method);
        con(conformui, seed);
        conclk(conformui, exit);
        conclk(conformui, runit);
        conwin(progressui);
        conclk(progressui, stop);
        con(progressui, textstat);
        con(progressui, sub);
        conwin(stopui);
        conclk(stopui, yes);
        conclk(stopui, exit);
        conclk(stopui, ok);
        conwin(texterrui);
        conclk(texterrui, exit);
        con(texterrui, tips);
        conwin(resultui);
        con(resultui, text);
        result_buf = GTK_TEXT_BUFFER(gtk_builder_get_object(builder, "result_buf"));

        concb_list = malloc_se(sizeof(GtkCheckButton *) * 8);
        concb_list[0] = conformui.chkb_dev;
        concb_list[1] = conformui.chkb_size;
        concb_list[2] = conformui.chkb_fromto;
        concb_list[3] = conformui.chkb_writesize;
        concb_list[4] = conformui.chkb_method;
        concb_list[5] = conformui.chkb_seed;
        concb_list[6] = conformui.chkb_final;
        concb_list[7] = NULL;

        #undef concb
        #undef conclk
        #undef conwin
        #undef con
        #undef TERM
        #undef DTY
        #undef CLK
}

int gui_main(int ac, char **av) {
        gtk_init(&ac, &av);

        builder = gtk_builder_new();
        GError *gerr = NULL;
        char *ui_path = exe_file_path(UI_FILE);
        if (gtk_builder_add_from_file(builder, ui_path, &gerr) == 0) {
                g_printerr("UI file `%s`: %s\n", ui_path, gerr->message);
                g_error_free(gerr);
                return 1;
        }
        free(ui_path);
        job_listen.filepath = NULL;

        med_list[0].tips = _("Seq test");
        med_list[1].tips = _("Seed test");
        med_list[2].tips = _("Seq WRITE");
        med_list[3].tips = _("Seed WRITE");
        med_list[4].tips = _("Random WRITE");

        gtk_obj_get();
        mainui_show();
        gtk_window_set_default_size(GTK_WINDOW(resultui.win), 300, 200);
        gtk_window_set_default_size(GTK_WINDOW(progressui.win), 300, 300);
        gtk_main();

        g_object_unref(builder);
        return 0;
}

void mainui_show(void) {
        gtk_widget_show(GTK_WIDGET(mainui.win));
}
static uint64_t get_number(const char *get, bool *nan) {
        uint64_t ret = 0;
        if (*get == '\0') {
                *nan = true;
                return 0;
        }
        while (*get != '\0') {
                if ('0' <= *get && *get <= '9') {
                        ret *= 10;
                        ret += *get - '0';
                        get++;
                } else {
                        *nan = true;
                        return ret;
                }
        }
        *nan = false;
        return ret;
}
static char *file_conform() {
        if (access(job_listen.filepath, F_OK)) {
                return _("No such file or directory");
        } else {
                if (access(job_listen.filepath, R_OK|W_OK))
                        return _("Permission denied");
                if (job_listen.fp)
                        fclose(job_listen.fp);
                if ((job_listen.fp = fopen(job_listen.filepath, "rb+")) == NULL)
                        return _("Permission denied");
        }
        fseeko64(job_listen.fp, 0, SEEK_END);
        job_listen.size = ftello64(job_listen.fp);
        if (job_listen.size <= 0) {
                return _("Cautiously reject empty files and files with unreadable sizes");
        }
        job_listen.count = job_listen.size;
        job_listen.from = 0;
        job_listen.to = job_listen.size - 1;
        return NULL;
}
void mainui_runit(void) {
        if (gworking)
                return;
        // FUNC
        GtkStack *stack_function = gtk_stack_switcher_get_stack(mainui.function);
        const char *function_name = gtk_stack_get_visible_child_name(stack_function);
        // MED
        GtkStack * stack_method = gtk_stack_switcher_get_stack(
                strequ(function_name, "test") ? mainui.test_method : mainui.write_method
        );
        const char *method_name = gtk_stack_get_visible_child_name(stack_method);
        for (int i = 0 ; true ; i ++) {
                if (med_list[i].name == NULL) {
                        fprintf(stderr, _("Method iist NOT include target, It is a InnerErr\n"));
                        abort();
                }
                if (strequ(method_name, med_list[i].name)) {
                        job_listen.job = med_list[i].job;
                        break;
                }
        }
        if (job_listen.job != WRAND) {
                GtkEntry *input = GTK_ENTRY(gtk_builder_get_object(builder, method_name));
                bool nan;
                job_listen.seed = get_number(gtk_entry_get_text(input), &nan);
                if (nan) {
                        texterrui_show(_("You should put a number"));
                        return;
                }
        }
        free(job_listen.filepath);
        if (job_listen.fp) {
                fclose(job_listen.fp);
                job_listen.fp = NULL;
        }
        job_listen.filepath = strdup(gtk_entry_get_text(mainui.dev));
        char *fc_err;
        if ((fc_err = file_conform()) == NULL)
                conformui_show();
        else
                texterrui_show(fc_err);
}
gboolean mainui_exit(void) {
        if (gworking)
                stopui_show();
        else
                gtk_main_quit();
        return TRUE;
}
char *itos(uint64_t n, char *buf) {
        sprintf(buf, "%ld", n);
        buf += strlen(buf);
        return buf;
}
void conformui_show(void) {
        for (int i = 0 ; concb_list[i] != NULL ; i ++)
                gtk_toggle_button_set_active(
                        GTK_TOGGLE_BUTTON(concb_list[i]), FALSE);
        gtk_widget_hide(GTK_WIDGET(conformui.runit));
        char buf[100];
        
        char *real_path = realpath(job_listen.filepath, NULL);
        gtk_label_set_text(conformui.dev, real_path);
        free(real_path);

        itos(job_listen.size, buf);
        gtk_label_set_text(conformui.size, buf);

        snprintf(buf, 99, "%ld / %ld", job_listen.from, job_listen.to);
        gtk_label_set_text(conformui.fromto, buf);

        itos(job_listen.count, buf);
        gtk_label_set_text(conformui.writesize, buf);

        gtk_label_set_text(conformui.method, med_list[job_listen.job].tips);

        itos(job_listen.seed, buf);
        gtk_label_set_text(conformui.seed, buf);

        gtk_widget_show(GTK_WIDGET(conformui.win));
}
void conformui_chkb(void) {
        bool can_go = true;
        for (int i = 0 ; concb_list[i] != NULL ; i++) {
                can_go &= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(concb_list[i]));
        }
        if (can_go)
                gtk_widget_show(GTK_WIDGET(conformui.runit));
        else
                gtk_widget_hide(GTK_WIDGET(conformui.runit));
}
gboolean conformui_exit(void) {
        gtk_widget_hide(GTK_WIDGET(conformui.win));
        return TRUE;
}
void conformui_runit(void) {
        // 兼容CLI版本
        gworking = true;
        opt.fp = job_listen.fp;
        opt.start = job_listen.from;
        opt.size = job_listen.size;
        opt.end = job_listen.to;
        switch (job_listen.job) {
        case TSEQ:
                opt.random = false;
                opt.test = true;
                opt.logLevel = 0;
                opt.seed = job_listen.seed;
                opt.seeded = true;
                break;
        case TSEED:
                opt.random = true;
                opt.test = true;
                opt.logLevel = 0;
                opt.seed = job_listen.seed;
                opt.seeded = true;
                break;
        case WSEQ:
                opt.random = false;
                opt.test = false;
                opt.logLevel = 0;
                opt.seed = job_listen.seed;
                opt.seeded = true;
                break;
        case WSEED:
                opt.random = true;
                opt.test = false;
                opt.logLevel = 0;
                opt.seed = job_listen.seed;
                opt.seeded = true;
                break;
        case WRAND:
                opt.random = true;
                opt.test = false;
                opt.logLevel = 0;
                opt.seeded = false;
                break;
        }
        // 线程创建
        break_log = malloc_se(sizeof(struct break_collect));
        break_log_start = break_log;
        break_collect_to = 0;
        memset(break_log, 0, sizeof(struct break_collect));
        gtk_text_buffer_set_text(result_buf, "", -1);
        time_t now = time(NULL);
        log_out(ctime(&now));
        log_out("\n");
        g_timeout_add(1000, gui_loop_process, NULL);
        static thrd_t cli_task;
        if (!thrd_se(&cli_task, task, &_false)) {
                log_out(_("Thread create error"));
                gworking = false;
        }
        // UI
        gtk_widget_hide(GTK_WIDGET(conformui.win));
        progressui_show();
}
void progressui_show(void) {
        gtk_widget_show(GTK_WIDGET(progressui.win));
}
void progressui_stop(void) {
        stopui_show();
}
void progressui_exit(void) {
        gtk_widget_hide(GTK_WIDGET(progressui.win));
}
void stopui_show(void) {
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(stopui.yes), FALSE);
        gtk_widget_hide(GTK_WIDGET(stopui.ok));
        gtk_widget_show(GTK_WIDGET(stopui.win));
}
void stopui_yes(void) {
        if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(stopui.yes)))
                gtk_widget_show(GTK_WIDGET(stopui.ok));
        else
                gtk_widget_hide(GTK_WIDGET(stopui.ok));
}
gboolean stopui_exit(void) {
        gtk_widget_hide(GTK_WIDGET(stopui.win));
        return TRUE;
}
void stopui_ok(void) {
        gworking = false;
        time_t now = time(NULL);
        sleep(1);
        log_out(ctime(&now));
        log_out(_("that time stop\n"));
        gtk_widget_hide(GTK_WIDGET(stopui.win));
}
void texterrui_show(char *tip) {
        gtk_label_set_text(texterrui.tips, tip);
        gtk_widget_show(GTK_WIDGET(texterrui.win));
}
gboolean texterrui_exit(void) {
        gtk_widget_hide(GTK_WIDGET(texterrui.win));
        return TRUE;
}
void resultui_show() {
        gtk_widget_show(GTK_WIDGET(resultui.win));
}
gboolean resultui_exit(void) {
        gtk_widget_hide(GTK_WIDGET(resultui.win));
        return TRUE;
}

static void break_log_fresh(void) {
        while (break_log->used > break_collect_to
            || break_log->next) {
                if (break_log->used == break_collect_to) {
                        break_collect_to = 0;
                        break_log = break_log->next;
                        continue;
                }
                struct break_log *log = &(break_log->log[break_collect_to]);
                char buf[100];
                sprintf(buf, _("Break %zd to %zd\n"), log->start, log->end);
                log_out(buf);
                break_collect_to ++;
        }
}
static void break_collect_free(void) {
        struct break_collect *next = break_log_start;
        while (next) {
                struct break_collect *pass = next->next;
                free(next);
                next = pass;
        }
}
static int gui_loop_process(void *_nil) {
        (void)_nil;
        // Caution, log may not be fully fresh after gworking change to false
        // So it is neccessry to write two break_log_fresh
        if (gworking) {
                break_log_fresh();
                gtk_progress_bar_set_fraction(progressui.sub, (float)(*head - opt.start) / (opt.size));

                char buf[100];
                sprintf(buf, "%ld / %ld / %ld", opt.start, *head, opt.end);
                gtk_label_set_text(progressui.textstat, buf);
                return TRUE;
        } else {
                break_log_fresh();
                progressui_exit();
                resultui_show();
                break_collect_free();
                return FALSE;
        }
}

static void log_out(char *log) {
        GtkTextBuffer *gbuf = result_buf;
        GtkTextIter end_iter;
        gtk_text_buffer_get_end_iter(gbuf, &end_iter);
        gtk_text_buffer_insert(gbuf, &end_iter, log, -1);
}

void mainui_help(void) {
        gtk_widget_show(GTK_WIDGET(aboutpage.win));
}

gboolean aboutpage_exit(void) {
        gtk_widget_hide(GTK_WIDGET(aboutpage.win));
        return TRUE;
}

#else // !_GUI
bool _NO_GUI_VERSION_ = true;
#define _NO_GUI_VERSION_ (1)
#endif // !_GUI
